/* 
 * File:   ModuleLoader.cpp
 * Author: pkv
 * 
 * Created on July 30, 2011, 5:40 PM
 * 
 * Implements a dynamic module loading subsystem that can load modules from
 * shared libraries.
 */

#include "DynamicLibrary.h"
#include "ModuleLoader.h"
#include "Logger.h"
#include "Filesystem.h"

Mutex ModuleLoader::_moduleLock;
std::map<std::string, ModuleLoader::ModuleRef> ModuleLoader::_modules;

SLB3_IMPLEMENTATION(ModuleLoader, c) {
  c.constructor()
/*
      .set("load", &ModuleLoader::load)
      .set("unload", &ModuleLoader::unload)
      .set("list", &ModuleLoader::list)
*/
      ;
}

inline std::string ModuleLoader::registerModuleName(std::string name) {
    std::string base = "RegisterModule_";
    base += name;
    return base;
}

inline std::string ModuleLoader::unregisterModuleName(std::string name) {
    std::string base = "UnregisterModule_";
    base += name;
    return base;
}

void *ModuleLoader::load(const std::string moduleName, void *param, const std::string path)
{
    Lock lock(_moduleLock);
    void *result = NULL;
    std::string tmpFile;
    
    // If the module is already loaded, continue as before
    if (_modules.count(moduleName) > 0) {
        return NULL;
    }
    
    // Create the appropriate file extension
    std::string libFilename = GetLibraryName(moduleName);
    
    // If the filesystem is set up, use it to search for a virtual path
    Filesystem fs;
    std::string libFilepath = (path.empty() ? "" : path + fs.getPathSeparator()) + libFilename;
    if (fs.exists(libFilepath)) {
        
        // Load the library into a temporary file and replace the filename
        tmpFile = fs.openTemp(libFilepath);
        if (!tmpFile.empty())
            libFilename = tmpFile;
    }
    
    // Try to load the module, and report failure
    void *modulePtr = LoadSharedLibraryFile(libFilename);
    if (modulePtr == NULL) 
    {
        LOG(Log::WARNING) << "Failed to load module: " << moduleName;
        return NULL;
    }
    
    // Call the module's registration function (but continue on failure)
    Module::Definition::RegisterFn registerModule = 
            (Module::Definition::RegisterFn)GetFunction(modulePtr, registerModuleName(moduleName));
    if (registerModule == NULL) 
    {
        LOG(Log::WARNING) << "Failed to find registration function for module: " << moduleName;
    }
    else 
    {
        result = registerModule(param);
    }
    
    // Add the module to the loaded list
    _modules[moduleName].isShared = true;
    _modules[moduleName].ptr.lib = modulePtr;
    _modules[moduleName].tmpFile = tmpFile;
    
    return result;
}

void *ModuleLoader::unload(const std::string moduleName, void *param)
{
    Lock lock(_moduleLock);
    void *result = NULL;
    
    // If no such module exists, we are done
    if (_modules.count(moduleName) < 1)
        return NULL;
    
    // Handle unregistration either through library or descriptor
    ModuleRef ref = _modules[moduleName];
    if (ref.isShared) 
    {
        // Call the module's un-registration function
        Module::Definition::UnregisterFn unregisterModule = 
                (Module::Definition::UnregisterFn)GetFunction(ref.ptr.lib, unregisterModuleName(moduleName));
        if (unregisterModule == NULL) 
        {
            LOG(Log::WARNING) << "Failed to find un-registration function for module: " << moduleName;
        }
        else 
        {
            result = unregisterModule(param);
        }

        // Attempt to unload the module, and record success/failure 
        if (!UnloadSharedLibrary(ref.ptr.lib))
        {
            LOG(Log::WARNING) << "Failed to unload module cleanly: " << moduleName;
        }
        
        // If a temp file was created, destroy it now
        if (!ref.tmpFile.empty()) 
        {
            Filesystem fs;
            fs.del(ref.tmpFile);
        }
    }
    else 
    {
        // Call the module's un-registration function
        result = ref.ptr.def->unregisterFn(param);
    }
    
    // Remove the module from the loaded list in any case, and return result
    _modules.erase(moduleName);
    return result;
}

std::vector<std::string> ModuleLoader::list()
{
    Lock lock(_moduleLock);
    
    // Copy each map entry to a vector and return it.  This isn't the most
    // efficient thing to do, but people hopefully aren't accessing the list
    // that frequently.
    // TODO: optimize retrieval of module list by removing copying
    std::vector<std::string> moduleNames;
    
    std::map<std::string, ModuleRef>::iterator itr;
    for (itr = _modules.begin(); itr != _modules.end(); ++itr)
    {  
        moduleNames.push_back(itr->first);
    }
    
    return moduleNames;
}
